package sli.sli2.pcp.tcp;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Vector;

import org.bouncycastle.openssl.PEMReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sli.sli2.channels.Channel;
import sli.sli2.domain.SecuredObjectKey;
import sli.sli2.pcp.service.AuthenticationService;
import sli.sli2.pcp.service.LoggingService;
import sli.sli2.pcp.service.TCPRequestService;

/**
 * Handles the TCP connection to a specific secured object, spawns a service class that handles authentication 
 * @author Group 8, Khassraf Roman, Mueller Robin, Zischka Stefan
 *
 */
public class TCPThread implements Runnable {

    private Socket socket;
    private BufferedReader reader;
    private PrintWriter writer;
    private String pathToPubkeysFolder;
    private static final Logger log = LoggerFactory.getLogger(TCPThread.class);
    
    private Vector<SecuredObjectKey> publicKeys = new Vector<SecuredObjectKey>();
	private PrivateKey providerPrivateKey;
	private LoggingService loggingService;
    
	public TCPThread(Socket socket, PrivateKey providerPrivatekey, LoggingService loggingService, String pathToPubkeysFolder) {
        try {
            this.socket = socket;
            this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            this.writer = new PrintWriter(socket.getOutputStream(), true);
            this.providerPrivateKey = providerPrivatekey;
            this.loggingService = loggingService;
            this.pathToPubkeysFolder = pathToPubkeysFolder;
        } catch (IOException ex) {
            log.error("Failed to instantiate BufferedReader", ex);
        }
	}
	
    @Override
	public void run() {
		log.info("TCPThread for {} port {} started.", socket.getInetAddress().getHostAddress(), socket.getPort());
        
		readPublicKeys();
		
		AuthenticationService as = new AuthenticationService(reader, writer, providerPrivateKey, publicKeys, loggingService);
        TCPRequestService rs = new TCPRequestService(loggingService);
        Channel channel;
		try {
			channel = as.authenticate();
		} catch (Exception ex) {
			log.error("Error at authentication", ex);
			return;
		}
        String line;
        while (!socket.isClosed()) {
            try {
                line = channel.receive();
                String response = rs.respond(line);
                channel.send(response.getBytes());
            } catch (SocketException ex) {
                if (ex.getMessage().equals("Connection reset")) {
                    log.info("Connection reset by client.");
                    loggingService.addLogEntry(as.getObjectId(), 0, "Connection to secured object closed unexpectedly");
                    loggingService.addLogEntryAndNotify(as.getObjectId(), 0, "Connection to secured object closed unexpectedly", "technical staff", "message");
                    return;
                }
                log.error("Error while receiving", ex);
                return;
            } catch (Exception ex) {
                log.error("Error while receiving", ex);
                return;
			}
        }
		
	}

	private void readPublicKeys() {
		
		File pubkeyFolder = new File(pathToPubkeysFolder);
		
		File[] files = pubkeyFolder.listFiles();
		
		for (File f : files) {
			
			PEMReader in;
			PublicKey pubkey = null;
			
			try {
				in = new PEMReader(new FileReader(f.getPath()));
				pubkey = (PublicKey) in.readObject();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();		
			}
		
			int id = 0;
			try {
				id = Integer.parseInt(f.getName().substring(0, f.getName().indexOf(".")));
			}
			catch (NumberFormatException nfe) {
				log.warn("Invalid key file name: {}", f.getName());
			}
			
			SecuredObjectKey sok = new SecuredObjectKey(id, pubkey);
			publicKeys.add(sok);
		}
	}

}
